package com.xyoto.community.service.impl;

import com.xyoto.community.config.PictureConfig;
import com.xyoto.community.constant.*;
import com.xyoto.community.dto.CheckContentDTO;
import com.xyoto.community.dto.DeletePictureDTO;
import com.xyoto.community.dto.UploadPictureDTO;
import com.xyoto.community.entity.picture.Picture;
import com.xyoto.community.enums.CommonEnum;
import com.xyoto.community.enums.FileEnum;
import com.xyoto.community.enums.PictureEnum;
import com.xyoto.community.enums.WechatLoginEnum;
import com.xyoto.community.exception.ExceptionUtils;
import com.xyoto.community.mapper.picture.DynamicPictureMapper;
import com.xyoto.community.mapper.picture.PictureMapper;
import com.xyoto.community.pojo.ContentCheckResult;
import com.xyoto.community.pojo.Type2PictureInfo;
import com.xyoto.community.service.PictureCacheService;
import com.xyoto.community.service.PictureService;
import com.xyoto.community.utils.ContentCheckUtils;
import com.xyoto.community.utils.RedisCommonUtils;
import com.xyoto.community.utils.JwtUtils;
import com.xyoto.community.utils.Result;
import com.xyoto.community.utils.XYOtoOss;
import com.xyoto.community.vo.UploadPictureVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class PictureServiceImpl implements PictureService {

    @Autowired
    private PictureMapper pictureMapper;

    @Autowired
    private ContentCheckUtils contentCheckUtils;

    @Resource
    private XYOtoOss xyOtoOss;

    @Autowired
    private PictureConfig pictureConfig;

    @Resource
    private RedisCommonUtils redisCommonUtils;

    @Resource
    private DynamicPictureMapper dynamicPictureMapper;

    @Resource
    private HttpServletRequest httpServletRequest;

    @Autowired
    private PictureCacheService pictureCacheService;

    private Random random = new Random();

    /**
     * 统一图片上传
     *
     * @param uploadPictureDTO
     * @return
     */
    @Override
    public Result uploadPicture(UploadPictureDTO uploadPictureDTO) {

        Integer userId = JwtUtils.getMemberIdByJwtToken(httpServletRequest);

        // 图片组虚拟id
        String pictureVirtualKey = uploadPictureDTO.getGroup();
        // 判断pictureVirtualKey在hash中是否存在，否则判定该判断pictureVirtualKey在hash中是否存在，若该图片组虚拟id不存在，则为非法的key
        if (!StringUtils.isEmpty(pictureVirtualKey) && !Boolean.TRUE.equals(redisCommonUtils.hasKey(pictureVirtualKey))) {
            // redis的hash中无该pictureVirtualKey
            ExceptionUtils.error(FileEnum.PICTURE_GROUP_IS_ILLEGAL);
        }

        // 检查图片的合法性
        checkPicture(uploadPictureDTO);

        // 根据type获取该类型图片的基本信息
        Type2PictureInfo pictureInfo = pictureConfig.getPictureInfoByType(uploadPictureDTO.getType());

        if (pictureInfo == null) {
            // 根据type没有拿到该类图片的基本信息
            ExceptionUtils.error(FileEnum.PICTURE_TYPE_NOT_EXIST);
        }

        // 上传图片到oss
        String path = null;
        try {
            path = xyOtoOss.uploadFileAvatar(uploadPictureDTO.getMultipartFile(), pictureInfo.getDirectoryName());
        } catch (IOException e) {
            log.warn("【图片上传】上传图片到oss失败，请求参数 = {}", uploadPictureDTO);
        }
        if (path == null) {
            // 上传文件到oss失败
            ExceptionUtils.error(FileEnum.FILE_UPLOAD_FAIL);
        }
        // 将图片记录存储到数据库
        Picture picture = new Picture();
        picture.setOrders(uploadPictureDTO.getOrders());
        picture.setUserId(userId);
        picture.setPath(path);
        Integer insert = pictureMapper.insertOne(picture,pictureInfo.getTableName());
        if (insert < DatabaseOperationResult.SUCCESS){
            ExceptionUtils.error(FileEnum.FILE_UPLOAD_FAIL);
        }

        if (StringUtils.isEmpty(pictureVirtualKey)) {
            // 本组图片上传的第一张，还没有虚拟id，生成一个
            // 生成缓存redis缓存对象， 对象结构为 key = pictureVirtualKey,tableName ,value = null
            pictureVirtualKey = UUID.randomUUID().toString().replaceAll("-", "");
            pictureVirtualKey += "," + pictureInfo.getTableName();
            // 将缓存对象放入redis并设置过期时间
            long pictureVirtualKeyExpireTime = PictureConstant.PICTURE_VIRTUAL_KEY_EXPIRE_TIME
                    + random.nextInt((int) PictureConstant.PICTURE_VIRTUAL_KEY_EXPIRE_TIME);
            redisCommonUtils.set(pictureVirtualKey, "", pictureVirtualKeyExpireTime, TimeUnit.SECONDS);
        }

        // 将pictureVirtualKey拼接上当前用户id，作为hash结构中的key
        // 这个hash结构就是真正存储当前用户已上传图片的id组的结构
        // 根据pictureVirtualKey从redis中拿到图片id集合
        List<Integer> pictureIdFromCache = (List<Integer>) redisCommonUtils
                .hget(PictureConstant.PICTURE_ID_CACHE_HASH_NAME, pictureVirtualKey);
        if (pictureIdFromCache == null){
            // 说明是第一次上传图片
            pictureIdFromCache = new ArrayList<>();
        }
        // 将图片id放入到redis的hash里面key为pictureVirtualKey的集合中
        pictureIdFromCache.add(picture.getId());
        redisCommonUtils.hset(PictureConstant.PICTURE_ID_CACHE_HASH_NAME,pictureVirtualKey,pictureIdFromCache);
        // 将图片id和图片分组标识返回给前端
        UploadPictureVo uploadPictureVo = new UploadPictureVo();
        uploadPictureVo.setPictureId(picture.getId());
        uploadPictureVo.setPictureVirtualKey(pictureVirtualKey);
        uploadPictureVo.setPath(path);
        //将图片添加到缓存中  == 异步
        pictureCacheService.addPictureToCache(picture.getId(),path);
        return Result.success(uploadPictureVo);
    }

    /**
     * 统一图片删除
     *
     * @param deletePictureDTO
     * @return
     */
    @Override
    public Result deletePicture(DeletePictureDTO deletePictureDTO) {
        Integer userId = JwtUtils.getMemberIdByJwtToken(httpServletRequest);
        if (userId == UserLoginStatus.UN_LOGIN) {
            // 用户未登录
            ExceptionUtils.error(WechatLoginEnum.USER_NOT_LOGIN);
        }

        // 根据图片类型获取对应图片类型的表名
        Type2PictureInfo pictureInfoByType = pictureConfig.getPictureInfoByType(deletePictureDTO.getType());
        if (pictureInfoByType == null) {
            ExceptionUtils.error(FileEnum.PICTURE_TYPE_NOT_EXIST);
        }
        // 情况1：内容发布前，删除已上传的图片，必须要有group，同时需要删redis和数据库
        // 情况2：内容发布后，删除已上传的新图片，必须要有group，同时需要删redis和数据库
        String pictureVirtualKey = deletePictureDTO.getGroup();
        if (!StringUtils.isEmpty(pictureVirtualKey)) {
            // 有group，判断group是否有效
            Object pictureIdFromCache = redisCommonUtils.hget(
                    PictureConstant.PICTURE_ID_CACHE_HASH_NAME, pictureVirtualKey);
            if (pictureIdFromCache == null) {
                // 前端传来了group，但是redis里面没有图片组id集合
                ExceptionUtils.error(FileEnum.PICTURE_GROUP_IS_ILLEGAL);
            }
            // 从图片id组中删除指定的图片id
            List<Integer> pictureIdFromCache2List = (List) pictureIdFromCache;
            boolean remove = pictureIdFromCache2List.remove(deletePictureDTO.getPictureId());
            if (!remove) {
                // 没有redis里面删除任何元素
                ExceptionUtils.error(FileEnum.PICTURE_DELETED);
            }
            // 更新redis
            redisCommonUtils.hset(PictureConstant.PICTURE_ID_CACHE_HASH_NAME, pictureVirtualKey, pictureIdFromCache2List);
            // 从数据库中查询出该图片
            Picture picture = pictureMapper.selectOne(deletePictureDTO.getPictureId(), pictureInfoByType.getTableName());
            if (picture == null) {
                // 数据库中无该图片
                ExceptionUtils.error(FileEnum.PICTURE_DELETED);
            }
            // 删除oss中的该图片
            xyOtoOss.deleteFileAvatar(picture.getPath());
            // 更新数据库
            pictureMapper.deleteOne(picture.getId(),userId,pictureInfoByType.getTableName());
        } else {
            // 没有group说明删除的是旧图片，需要判断该图片是否属于该用户
            Integer delete = pictureMapper.deleteOne(deletePictureDTO.getPictureId(),userId, pictureInfoByType.getTableName());
            if (delete < DatabaseOperationResult.SUCCESS){
                // 删除操作影响到的表记录行数为0
                ExceptionUtils.error(FileEnum.PICTURE_DELETED);
            }
        }
        // 情况3：内容发布后，删除已上传的旧图片，不需要有group，只需要删数据库
        //删除缓存中的图片信息
        pictureCacheService.deletePictureFromCache(deletePictureDTO.getPictureId());
        return Result.success();
    }

    /**
     * 检查文本内容是否合法
     * @param checkContentDTO
     * @return
     */
    @Override
    public Result checkContent(CheckContentDTO checkContentDTO) {
        if (checkContentDTO.getContent() == null){
            ExceptionUtils.error(CommonEnum.REQUEST_PARAM_ERROR);
        }
        ContentCheckResult contentCheckResult = contentCheckUtils.checkContent(checkContentDTO.getContent());
        if (contentCheckResult != null){
            //文本存在违规现象
            ExceptionUtils.error(CommonEnum.CONTENT_NOT_VALIDATE);
        }
        return Result.success();
    }

    /**
     * 前端上传图片到oss
     * @param multipartFile
     * @return
     */
    @Override
    public Result uploadFrontIcon(MultipartFile multipartFile) {
        UploadPictureDTO uploadPictureDTO = new UploadPictureDTO();
        uploadPictureDTO.setMultipartFile(multipartFile);
        checkPicture(uploadPictureDTO);
        String url = "";
        String fileName = multipartFile.getOriginalFilename();
        try {
            url = xyOtoOss.uploadFileAvatars(new MultipartFile[]{multipartFile}, OssBucketModuleName.ICON);
        } catch (IOException e) {
            e.printStackTrace();
        }
        HashMap<String, String> data = new HashMap<>();
        data.put("url",url);
        data.put("fileName",fileName);
        return Result.success(data);
    }

    /**
     * 图片合法性检查
     *
     * @param uploadPictureDTO
     */
    @Override
    public void checkPicture(UploadPictureDTO uploadPictureDTO) {

        // 检查图片大小
        MultipartFile multipartFile = uploadPictureDTO.getMultipartFile();
        if (multipartFile == null) {
            // 图片为空
            ExceptionUtils.error(FileEnum.FILE_IS_NULL);
        }
        if (multipartFile.getSize() > FileConstants.MAX_SIZE) {
            // 文件过大
            ExceptionUtils.error(FileEnum.FILE_SIZE_OVERFLOW);
        }
        // 调用微信接口检查
        ContentCheckResult contentCheckResult = contentCheckUtils.checkImg(multipartFile);
        if (contentCheckResult != null) {
            // 图片存在违规情况
            ExceptionUtils.error(FileEnum.PICTURE_IS_ILLEGAL);
        }
    }

    @Override
    public void updateBathPictureKeyId(String key, Integer id) {
        //判断key是否为空
        if (StringUtils.isNotBlank(key)){
            //判断该key是否存在
            if (redisCommonUtils.hasKey(key)){
                String[] split = key.split(",");
                //获取表名
                String tableName = split[1];
                List pictureIds = (List)redisCommonUtils.hget(PictureConstant.PICTURE_ID_CACHE_HASH_NAME,
                        key);
                //更新图片到数据库
                Integer integer = dynamicPictureMapper.updateBathPictureKeyId(tableName,id,pictureIds);
                if (integer < DatabaseOperationResult.SUCCESS){
                    ExceptionUtils.error(PictureEnum.PICTURE_KEY_UPDATE_FAIL);
                }
                //删除过期key
                redisCommonUtils.del(key);
                //删除hash中的内部key值
                redisCommonUtils.hdel(PictureConstant.PICTURE_ID_CACHE_HASH_NAME,key);
                return;
            }
            ExceptionUtils.error(PictureEnum.PICTURE_ALREADY_EXPIRE);
        }
    }
}
